import { ArrayTool, BiFunction, ObjectTool, Predicate, StrTool, Supplier } from '@es-tool/core'
import { Query } from 'database-ql/dist/commonjs/query'
import { Db } from 'laf-client-sdk'
import { Sealed } from './annotation/Sealed'
import { OrderBy } from './type/OrderBy'

// noinspection JSUnusedGlobalSymbols
/**
 * QueryChain 基础查询链
 * 不对外开放
 * @author LL
 * @date 2022-02-12 上午 10:36
 * @param E 当前操作的表模型 类型
 **/
@Sealed
export class QueryChain<E> {

    /**
     * 数据库引用
     * @type {}
     * @private
     */
    protected readonly database: Db

    /**
     * 查询条件缓存: where
     * @type {Record<keyof E, any>}
     * @private
     */
    protected tempQuery: Record<keyof E, E[keyof E] & any> = {} as any
    protected whereCache: Record<string, any>
    /**
     * 显示隐藏列缓存
     * @type {Record<keyof E, boolean>} K: 列名, E[C]: true 显示, false 隐藏
     * @private
     */
    protected tempField: Record<keyof E, boolean> = {} as any
    /**
     * 排序缓存
     * @type {Record<keyof E, OrderBy>}
     * @private
     */
    protected tempOrder: Record<keyof E, OrderBy> = {} as any

    protected orFlag: boolean = false
    protected orQuery: Record<keyof E, any>[] = []

    constructor(database: Db) {
        this.database = database
    }

    /**
     * 部分兼容原 where 参数 <br/>
     * 这里传入的参数会和构造参数合并, 同名属性以 where 优先 <br/>
     * 注意: 使用 {@link #orAll()} 会导致此参数无效 <br/>
     * 和楼下 {@link #whereCmd} 不兼容 会按执行顺序覆盖
     *@param where
     * @returns {QueryChainWrapper<E>}
     */
    public where(where: Partial<Record<keyof E, any>>) {
        this.whereCache = where
        return this
    }

    /**
     * 部分兼容原 where 参数, 提供 DB 对象引用 方便使用 db.command <br/>
     * 这里传入的参数会和构造参数合并, 同名属性以 where 优先 <br/>
     * 注意: 使用 {@link #orAll()} 会导致此参数无效 <br/>
     * 和楼上 {@link #where} 不兼容 会按执行顺序覆盖
     *@param action
     */
    public whereCmd(action: BiFunction<Partial<Record<keyof E, any>>, Db, Partial<Record<keyof E, any>>>) {
        // @ts-ignore
        this.whereCache = action({}, this.database)
        return this
    }

    /* -------------------------------------------------------------------------------------- */

    /**
     * 相等
     */
    public eq<C extends keyof E>(column: C, value: E[C]) {
        this.tempQuery[column] = this.database.command.eq(value)
        return this
    }

    public eqIf<C extends keyof E>(predicate: Predicate<E[C] | undefined>, column: C, value?: E[C]) {
        if (predicate(value)) {
            // @ts-ignore
            this.eq(column, value)
        }
        return this
    }

    public eqNotNull<C extends keyof E>(column: C, value?: E[C]) {
        return this.eqIf(() => ObjectTool.isNotNull(value), column, value)
    }

    public eqNotEmpty<C extends keyof E>(column: C, value?: string) {
        return this.eqIf(() => StrTool.isNotEmpty(value), column, value as any)
    }

    /**
     * 不等
     */
    public neq<C extends keyof E>(column: C, value: E[C]) {
        this.tempQuery[column] = this.database.command.neq(value)
        return this
    }

    public neqIf<C extends keyof E>(predicate: Predicate<E[C] | undefined>, column: C, value?: E[C]) {
        if (predicate(value)) {
            // @ts-ignore
            this.neq(column, value)
        }
        return this
    }

    public neqNotNull<C extends keyof E>(column: C, value?: E[C]) {
        return this.neqIf(() => ObjectTool.isNotNull(value), column, value)
    }

    public neqNotEmpty<C extends keyof E>(column: C, value?: string) {
        return this.neqIf(() => StrTool.isNotEmpty(value), column, value as any)
    }

    /**
     * 小于
     */
    public lt<C extends keyof E>(column: C, value: E[C]) {
        this.tempQuery[column] = this.database.command.lt(value)
        return this
    }

    public ltIf<C extends keyof E>(predicate: Predicate<E[C] | undefined>, column: C, value?: E[C]) {
        if (predicate(value)) {
            // @ts-ignore
            this.lt(column, value)
        }
        return this
    }

    /**
     * 小于等于
     */
    public lte<C extends keyof E>(column: C, value: E[C]) {
        this.tempQuery[column] = this.database.command.lte(value)
        return this
    }

    public lteIf<C extends keyof E>(predicate: Predicate<E[C] | undefined>, column: C, value?: E[C]) {
        if (predicate(value)) {
            // @ts-ignore
            this.lte(column, value)
        }
        return this
    }

    /**
     * 大于
     */
    public gt<C extends keyof E>(column: C, value: E[C]) {
        this.tempQuery[column] = this.database.command.gt(value)
        return this
    }

    public gtIf<C extends keyof E>(predicate: Predicate<E[C] | undefined>, column: C, value?: E[C]) {
        if (predicate(value)) {
            // @ts-ignore
            this.gt(column, value)
        }
        return this
    }

    /**
     * 大于等于
     */
    public gte<C extends keyof E>(column: C, value: E[C]) {
        this.tempQuery[column] = this.database.command.gte(value)
        return this
    }

    public gteIf<C extends keyof E>(predicate: Predicate<E[C] | undefined>, column: C, value?: E[C]) {
        if (predicate(value)) {
            // @ts-ignore
            this.gte(column, value)
        }
        return this
    }

    /**
     * IN
     */
    public in<C extends keyof E>(column: C, value: E[C][]) {
        this.tempQuery[column] = this.database.command.in(value)
        return this
    }

    public inIf<C extends keyof E>(predicate: Predicate<E[C][] | E[C] | undefined>, column: C, value?: E[C][] | E[C]) {
        if (predicate(value)) {
            // @ts-ignore
            this.in(column, value)
        }
        return this
    }

    public inNotEmpty<C extends keyof E>(column: C, value?: E[C][] | E[C]) {
        // @ts-ignore
        return this.inIf(arr => ArrayTool.isNotEmpty(arr), column, value)
    }

    /**
     * Not In
     */
    public notIn<C extends keyof E>(column: C, value: E[C][]) {
        this.tempQuery[column] = this.database.command.nin(value)
        return this
    }

    public notInIf<C extends keyof E>(predicate: Predicate<Array<E[C]> | E[C] | undefined>, column: C, value?: Array<E[C]> | E[C]) {
        if (predicate(value)) {
            // @ts-ignore
            this.notIn(column, value)
        }
        return this
    }

    public notInNotEmpty<C extends keyof E>(column: C, value: Array<E[C]> | E[C]) {
        // @ts-ignore
        return this.notInIf(arr => ArrayTool.isNotEmpty(arr), column, value)
    }

    /**
     * 区间 (start, end)
     *@param column 列名
     *@param start 开始 不含
     *@param end 结束 不含
     * @return {this<E>} 返回自身
     */
    public between<C extends keyof E>(column: C, start: E[C], end: E[C]) {
        const cmd = this.database.command
        this.tempQuery[column] = cmd.gt(start).and(cmd.lt(end))
        return this
    }

    /**
     * 反区间 相当于 (start, end) 的反集
     *@param column 列名
     *@param start 开始 不含
     *@param end 结束 不含
     * @return {this<E>} 返回自身
     */
    public notBetween<C extends keyof E>(column: C, start: E[C], end: E[C]) {
        const cmd = this.database.command
        this.tempQuery[column] = cmd.lt(start).and(cmd.gt(end))
        return this
    }

    /**
     * 区间 [start, end]
     *@param column 列名
     *@param start 开始 包含
     *@param end 结束 包含
     * @return {this<E>} 返回自身
     */
    public betweenEqual<C extends keyof E>(column: C, start: E[C], end: E[C]) {
        const cmd = this.database.command
        this.tempQuery[column] = cmd.gte(start).and(cmd.lte(end))
        return this
    }

    /**
     * 反区间 [start, end]
     *@param column 列名
     *@param start 开始 包含
     *@param end 结束 包含
     * @return {this<E>} 返回自身
     */
    public notBetweenEqual<C extends keyof E>(column: C, start: E[C], end: E[C]) {
        const cmd = this.database.command
        this.tempQuery[column] = cmd.lte(start).and(cmd.gte(end))
        return this
    }

    /**
     * 模糊匹配 相当于: {@code .*value.*}
     *@param column 列名
     *@param value 关键字
     * @return {this<E>} 返回自身
     */
    public like<C extends keyof E>(column: C, value: string) {
        this.tempQuery[column] = new RegExp(`.*${value}.*`)
        return this
    }

    /**
     * 条件模糊匹配 相当于: {@code .*value.*}
     *@param predicate 条件谓词, 接收输入值 value 返回 boolean
     *@param column 列名
     *@param value 关键字
     * @return {this<E>} 返回自身
     */
    public likeIf<C extends keyof E>(predicate: Predicate<string | undefined>, column: C, value?: string) {
        if (predicate(value)) {
            // @ts-ignore
            this.like(column, value)
        }
        return this
    }

    /**
     * 预设条件模糊匹配, 只有 value 不是空字符串时候才会执行
     *@param column 列名
     *@param value 关键字
     * @return {this<E>} 返回自身
     */
    public likeNotEmpty<C extends keyof E>(column: C, value?: string) {
        return this.likeIf(str => StrTool.isNotEmpty(str), column, value)
    }

    /**
     * 左 Like 相当于: {@code .*value}
     *@param column 列名
     *@param value 关键字
     * @return {this<E>} 返回自身
     */
    public leftLike<C extends keyof E>(column: C, value: string) {
        this.tempQuery[column] = new RegExp(`.*${value}`)
        return this
    }

    /**
     * 条件 左 like 相当于: {@code .*value}
     *@param predicate 谓词, 输入 value 输出 boolean; 输出为 true 才会添加 letKike条件
     *@param column 列名
     *@param value 关键字
     * @return {this<E>} 返回自身
     */
    public leftLikeIf<C extends keyof E>(predicate: Predicate<string>, column: C, value: string) {
        if (predicate(value)) {
            this.leftLike(column, value)
        }
        return this
    }

    /**
     * 右 like 相当于 {@code value.*}
     *@param column 列名
     *@param value 关键字
     * @return {this<E>} 返回自身
     */
    public rightLike<C extends keyof E>(column: C, value: string) {
        this.tempQuery[column] = new RegExp(`${value}.*`)
        return this
    }

    /**
     * 条件右like 相当于 {@code value.*}
     *@param predicate 谓词, 输入value ,输出 boolean; 返回 true 才会添加 rightLike 条件
     *@param column 列名
     *@param value 关键字
     * @return {this<E>} 返回自身
     */
    public rightLikeIf<C extends keyof E>(predicate: Predicate<string>, column: C, value: string) {
        if (predicate(value)) {
            this.rightLike(column, value)
        }
        return this
    }

    /* -------------------------------------------------------------------------------------- */
    /**
     * 指定查询列 <br>
     * 对于 mongo  无论如何都会返回 _id <br>
     * 对于关联查询列, 如果被隐藏, 结果未定义 <br>
     * 和楼下 {@link #hide} 不兼容 不能同时使用
     *@param column 列名
     * @return {this<E>} 返回自身
     */
    public show<C extends keyof E>(...column: C[]) {
        column.forEach(c => this.tempField[c] = true)
        return this
    }

    /**
     * 指定隐藏列 <br>
     * 可以指定 _id 隐藏ID响应 <br>
     * 对于关联查询列, 如果被隐藏, 结果未定义 <br>
     * 和楼上 {@link #show} 不兼容 不能同时使用
     *@param column 列名
     * @return {this<E>} 返回自身
     */
    public hide<C extends keyof E>(...column: C[]) {
        column.forEach(c => this.tempField[c] = false)
        return this
    }

    /**
     * 指定升序
     *@param column 列名可变参数列表
     * @return {this<E>} 返回自身
     */
    public orderByAsc<C extends keyof E>(...column: C[]) {
        for (let c of column) {
            this.tempOrder[c] = OrderBy.ASC
        }
        return this
    }

    /**
     * 指定降序
     *@param column 列名可变参数列表
     * @return {this<E>} 返回自身
     */
    public orderByDesc<C extends keyof E>(...column: C[]) {
        for (let c of column) {
            this.tempOrder[c as C] = OrderBy.DESC
        }
        return this
    }

    /**
     * 动态指定排序
     *@param column 列名
     *@param supplier 升序/降序枚举提供者 {@link OrderBy}
     * @return {this<E>} 返回自身
     */
    public orderBy<C extends keyof E>(column: C, supplier: Supplier<OrderBy>) {
        this.tempOrder[column] = supplier()
        return this
    }

    /**
     * 跨列 or
     * TODO: 未定义
     */
    public orAll() {
        this.orFlag = true
        this.orQuery.push(this.tempQuery)
        this.tempQuery = {} as any
        return this
    }

    /**
     * 内部: 获取where的参数
     * @return {Record<any, any>}
     * @private
     */
    protected getWhereArg(): Record<any, any> {
        if (this.orFlag) {
            this.orQuery.push(this.tempQuery)
            return this.database.command.or(...this.orQuery)
        }
        if (ObjectTool.isNotEmpty(this.whereCache)) {
            return { ...this.tempQuery, ...this.whereCache }
        }
        return this.tempQuery
    }

    /**
     * 有限 的实现, 子类需要有选择的覆盖默认实现
     * @param connection
     * @return {any}
     * @protected
     */
    protected setField(connection: Query) {
        if (ObjectTool.isNotEmpty(this.tempField)) {
            // string[] | [field: string]: 0 | 1
            // @ts-ignore
            connection = connection.field(this.tempField)
        }
        return connection
    }

    protected setOrder(connection: Query) {
        if (ObjectTool.isNotEmpty(this.tempOrder)) {
            Object.keys(this.tempOrder)
                .forEach(key => {
                    connection = connection.orderBy(key, this.tempOrder[key])
                })
        }
        return connection
    }
}
